/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2022 Syarif Fakhri
 * Copyright (C) 2017 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockAutoHideSideBar.h"
#include "QxDockAutoHideContainer.h"
#include "QxDockAutoHideTab.h"
#include "QxDockAreaWidget.h"
#include "QxDockContainerWidget.h"
#include "QxDockFocusController.h"
#include "QxDockWidgetTab.h"
#include "QxDockStateReader.h"

#include <QBoxLayout>
#include <QPainter>
#include <QStyleOption>
#include <QXmlStreamWriter>

QX_BEGIN_NAMESPACE

class CTabsWidget;

/**
 * Private data class of CSideTabBar class (pimpl)
 */
struct DockAutoHideSideBarPrivate {
    /**
     * Private data constructor
     */
    DockAutoHideSideBarPrivate(DockAutoHideSideBar *_public);

    DockAutoHideSideBar *_this;
    DockContainerWidget *ContainerWidget;
    CTabsWidget *TabsContainerWidget;
    QBoxLayout *TabsLayout;
    Qt::Orientation Orientation;
    SideBarLocation SideTabArea = SideBarLocation::SideBarLeft;

    /**
     * Convenience function to check if this is a horizontal side bar
     */
    bool isHorizontal() const
    {
        return Qt::Horizontal == Orientation;
    }

    /**
     * Called from viewport to forward event handling to this
     */
    void handleViewportEvent(QEvent *e);
};   // struct DockAutoHideSideBarPrivate

/**
 * This widget stores the tab buttons
 */
class CTabsWidget : public QWidget
{
public:
    using QWidget::QWidget;
    using Super = QWidget;
    DockAutoHideSideBarPrivate *EventHandler;

    /**
     * Returns the size hint as minimum size hint
     */
    virtual QSize minimumSizeHint() const override
    {
        return Super::sizeHint();
    }

    /**
     * Forward event handling to EventHandler
     */
    virtual bool event(QEvent *e) override
    {
        EventHandler->handleViewportEvent(e);
        return Super::event(e);
    }
};

DockAutoHideSideBarPrivate::DockAutoHideSideBarPrivate(DockAutoHideSideBar *_public) : _this(_public)
{
}

void DockAutoHideSideBarPrivate::handleViewportEvent(QEvent *e)
{
    switch (e->type()) {
    case QEvent::ChildRemoved:
        if (TabsLayout->isEmpty()) {
            _this->hide();
        }
        break;

    case QEvent::Resize:
        if (_this->tabCount()) {
            auto ev = static_cast<QResizeEvent *>(e);
            auto Tab = _this->tabAt(0);
            int Size = isHorizontal() ? ev->size().height() : ev->size().width();
            int TabSize = isHorizontal() ? Tab->size().height() : Tab->size().width();
            // If the size of the side bar is less than the size of the first tab
            // then there are no visible tabs in this side bar. This check will
            // fail if someone will force a very big border via CSS!!
            if (Size < TabSize) {
                _this->hide();
            }
        } else {
            _this->hide();
        }
        break;

    default:
        break;
    }
}

DockAutoHideSideBar::DockAutoHideSideBar(DockContainerWidget *parent, SideBarLocation area)
    : Super(parent), d(new DockAutoHideSideBarPrivate(this))
{
    d->SideTabArea = area;
    d->ContainerWidget = parent;
    d->Orientation =
        (area == SideBarLocation::SideBarBottom || area == SideBarLocation::SideBarTop) ? Qt::Horizontal : Qt::Vertical;

    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    setFrameStyle(QFrame::NoFrame);
    setWidgetResizable(true);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    d->TabsContainerWidget = new CTabsWidget();
    d->TabsContainerWidget->EventHandler = d;
    d->TabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    d->TabsContainerWidget->setObjectName("sideTabsContainerWidget");

    d->TabsLayout = new QBoxLayout(d->Orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
    d->TabsLayout->setContentsMargins(0, 0, 0, 0);
    d->TabsLayout->setSpacing(12);
    d->TabsLayout->addStretch(1);
    d->TabsContainerWidget->setLayout(d->TabsLayout);
    setWidget(d->TabsContainerWidget);

    setFocusPolicy(Qt::NoFocus);
    if (d->isHorizontal()) {
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    } else {
        setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
    }

    hide();
}

DockAutoHideSideBar::~DockAutoHideSideBar()
{
    QX_DOCK_PRINT("~CSideTabBar()");
    // The SideTabeBar is not the owner of the tabs and to prevent deletion
    // we set the parent here to nullptr to remove it from the children
    auto Tabs = findChildren<DockAutoHideTab *>(QString(), Qt::FindDirectChildrenOnly);
    for (auto Tab : Tabs) {
        Tab->setParent(nullptr);
    }
    delete d;
}

void DockAutoHideSideBar::insertTab(int Index, DockAutoHideTab *SideTab)
{
    SideTab->setSideBar(this);
    SideTab->installEventFilter(this);
    if (Index < 0) {
        d->TabsLayout->insertWidget(d->TabsLayout->count() - 1, SideTab);
    } else {
        d->TabsLayout->insertWidget(Index, SideTab);
    }
    show();
}

DockAutoHideContainer *DockAutoHideSideBar::insertDockWidget(int Index, DockWidget *dockWidget)
{
    auto AutoHideContainer = new DockAutoHideContainer(dockWidget, d->SideTabArea, d->ContainerWidget);
    dockWidget->dockManager()->dockFocusController()->clearDockWidgetFocus(dockWidget);
    auto Tab = AutoHideContainer->autoHideTab();
    dockWidget->setSideTabWidget(Tab);
    insertTab(Index, Tab);
    return AutoHideContainer;
}

void DockAutoHideSideBar::removeAutoHideWidget(DockAutoHideContainer *AutoHideWidget)
{
    AutoHideWidget->autoHideTab()->removeFromSideBar();
    auto DockContainer = AutoHideWidget->dockContainer();
    if (DockContainer) {
        DockContainer->removeAutoHideWidget(AutoHideWidget);
    }
    AutoHideWidget->setParent(nullptr);
}

void DockAutoHideSideBar::addAutoHideWidget(DockAutoHideContainer *AutoHideWidget)
{
    auto SideBar = AutoHideWidget->autoHideTab()->sideBar();
    if (SideBar == this) {
        return;
    }

    if (SideBar) {
        SideBar->removeAutoHideWidget(AutoHideWidget);
    }
    AutoHideWidget->setParent(d->ContainerWidget);
    AutoHideWidget->setSideBarLocation(d->SideTabArea);
    d->ContainerWidget->registerAutoHideWidget(AutoHideWidget);
    insertTab(-1, AutoHideWidget->autoHideTab());
}

void DockAutoHideSideBar::removeTab(DockAutoHideTab *SideTab)
{
    SideTab->removeEventFilter(this);
    d->TabsLayout->removeWidget(SideTab);
    if (d->TabsLayout->isEmpty()) {
        hide();
    }
}

bool DockAutoHideSideBar::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() != QEvent::ShowToParent) {
        return false;
    }

    // As soon as on tab is shown, we need to show the side tab bar
    auto Tab = qobject_cast<DockAutoHideTab *>(watched);
    if (Tab) {
        show();
    }
    return false;
}

Qt::Orientation DockAutoHideSideBar::orientation() const
{
    return d->Orientation;
}

DockAutoHideTab *DockAutoHideSideBar::tabAt(int index) const
{
    return qobject_cast<DockAutoHideTab *>(d->TabsLayout->itemAt(index)->widget());
}

int DockAutoHideSideBar::tabCount() const
{
    return d->TabsLayout->count() - 1;
}

SideBarLocation DockAutoHideSideBar::sideBarLocation() const
{
    return d->SideTabArea;
}

void DockAutoHideSideBar::saveState(QXmlStreamWriter &s) const
{
    if (!tabCount()) {
        return;
    }

    s.writeStartElement("SideBar");
    s.writeAttribute("Area", QString::number(sideBarLocation()));
    s.writeAttribute("Tabs", QString::number(tabCount()));

    for (auto i = 0; i < tabCount(); ++i) {
        auto Tab = tabAt(i);
        if (!Tab) {
            continue;
        }

        Tab->dockWidget()->autoHideDockContainer()->saveState(s);
    }

    s.writeEndElement();
}

QSize DockAutoHideSideBar::minimumSizeHint() const
{
    QSize Size = sizeHint();
    Size.setWidth(10);
    return Size;
}

QSize DockAutoHideSideBar::sizeHint() const
{
    return d->TabsContainerWidget->sizeHint();
}

int DockAutoHideSideBar::spacing() const
{
    return d->TabsLayout->spacing();
}

void DockAutoHideSideBar::setSpacing(int Spacing)
{
    d->TabsLayout->setSpacing(Spacing);
}

DockContainerWidget *DockAutoHideSideBar::dockContainer() const
{
    return d->ContainerWidget;
}

QX_END_NAMESPACE
